#include "Wire.h"
#include "Weather.h"
#include "Arduino.h"

// Float version of map
float mapf(float x, float in_min, float in_max, float out_min, float out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

const Weather::Waypoint Weather::m_shortCloud[SHORT_CLOUD_POINTS] = 
{
	{ 0, 0 },
	{ 3, 20 },   
	{ 10, 60 },   
	{ 15, 25 },   
	{ 20, 60 },   
	{ 30, 35 },   
	{ 40, 50 },  
	{ 50, 60 },  
	{ 60, 0  }    
	// Total time = 2min =  120secs or 60*2secs
};

// Cloud shape curve
const Weather::Waypoint Weather::m_longCloud[LONG_CLOUD_POINTS] = 
{
	{ 0, 0 },
	{ 8, 60 },   
	{ 15, 42 },   
	{ 30, 23 },   
	{ 40, 51 },   
	{ 50, 15 },   
	{ 60, 50 },  
	{ 100, 20 },  
	{ 125, 60 },   
	{ 150, 53 },  
	{ 175, 20 },   
	{ 200, 50 },  
	{ 225, 50 },   
	{ 250, 22 },  
	{ 290, 68 },  
	{ 300, 0  }    
	// Total time = 10min =  600secs or 300*2secs
};

// Thunderstorm cloud shape curve
const Weather::Waypoint Weather::m_thunderstormCloud[THUNDERSTORM_SHAPE_POINTS] = 
{
	{ 0, 0 } ,
	{ 90, 50 } ,    //180 seconds deep fade
	{ 270, 70 } ,   //360 seconds shallow fade
	{ 2070, 70 } ,  //3600 seconds level (1 hour)
	{ 2370, 50 } ,  
	{ 3300, 60 },
	{ 3600, 0  }    //600 seconds deep fade
	// total time = 7200 seconds = 3600*2secs =2 hours
};


const uint8_t Weather::m_clearDays[12] = { 15, 12, 20, 23, 28, 37, 43, 48, 51, 41, 29, 23 };    // From 0 to m_clearDays = clear day (oktas 0..1)
const uint8_t Weather::m_cloudyDays[12] = { 60, 61, 62, 60, 64, 63, 68, 66, 63, 54, 52, 53 };   // From m_clearDays to m_cloudyDays = cloudy day (oktas 4..8)

// Month Data for Start, Stop, Photo Period and Fade (based off of actual times, best not to change)
//Days in each month
const uint8_t Weather::m_daysInMonth[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };  

// Minimum and Maximum sunrise start times in each month
const unsigned int Weather::m_minSunriseStart[12] = { 296, 321, 340, 357, 372, 389, 398, 389, 361, 327, 297, 285 }; 
const unsigned int Weather::m_maxSunriseStart[12] = { 320, 340, 356, 372, 389, 398, 389, 361, 327, 297, 285, 296 }; 

// Minimum and Maximum sunset stop times each month
const unsigned int Weather::m_minSunsetFinish[12] = { 1126, 1122, 1101, 1068, 1038, 1022, 1025, 1039, 1054, 1068, 1085, 1108 }; 
const unsigned int Weather::m_maxSunsetFinish[12] = { 1122, 1101, 1068, 1038, 1022, 1025, 1039, 1054, 1068, 1085, 1108, 1126 }; 

// Minimum and Maximum sunrise or sunset fade duration in each month
const unsigned int Weather::m_minFadeDuration[12] = { 350, 342, 321, 291, 226, 173, 146, 110, 122, 139, 217, 282 }; 
const unsigned int Weather::m_maxFadeDuration[12] = { 342, 321, 291, 226, 173, 146, 110, 122, 139, 217, 282, 350 }; 


Weather::Weather(float maxWhite, float maxBlue)
{
	m_qtyClouds = 0;

	m_maxWhite = maxWhite;
	m_maxBlue = maxBlue;

	m_currCloudCoverStart  = 0;
	m_currCloudCoverFinish = 0;
}

void Weather::setCloudSpacingAndTypes()
{
    switch (m_okta) 
	{
	case 0:
      // No clouds, nothing to do....
      m_forecast = "clear";
      m_cloudSpacing = 0;
      break;
      
    case 1:
    case 2: // these days will be "short cloud + space"
      m_forecast = "partly cloudy";
      m_cloudSpacing=(8-m_okta) * getCloudDuration(SHORT_CLOUD);
      m_cloudType1=SHORT_CLOUD;
      m_cloudType2=SHORT_CLOUD;
      break;
      
    case 3:
    case 4: // these days will be "short cloud + space + long cloud + space"
      m_forecast = "mostly cloudy";
      m_cloudSpacing=(8-m_okta) * getCloudDuration(SHORT_CLOUD);
      m_cloudType1=SHORT_CLOUD;
      m_cloudType2=LONG_CLOUD;
      break;
      
    case 5: // Morning of short clouds spaced as an okta 2 day, followed by one thunderstorm in the afternoon;
      m_forecast = "partly cloudy. afternoon thunderstorm";
      m_cloudSpacing=6 * getCloudDuration(SHORT_CLOUD);
      m_cloudType1=SHORT_CLOUD;
      m_cloudType2=SHORT_CLOUD;
      break;
      
    case 6: // Morning of long clouds spaced as an okta 4 day, followed by one thunderstorm in the afternoon;
      m_forecast = "mostly cloudy. afternoon thunderstorm";
      m_cloudSpacing=4 * getCloudDuration(SHORT_CLOUD);
      m_cloudType1=LONG_CLOUD;
      m_cloudType2=LONG_CLOUD;
      break;
      
    case 7: // these days will be "long cloud + space"
      m_forecast = "very cloudy";
      m_cloudSpacing=2 * getCloudDuration(SHORT_CLOUD);
      m_cloudType1=LONG_CLOUD;
      m_cloudType2=LONG_CLOUD;
      break;
        
    case 8: // heavy thunderstorm day... one after the other with a short space between them
      m_forecast = "heavy thunderstorms";
      m_cloudSpacing=getCloudDuration(SHORT_CLOUD);
      m_cloudType1=THUNDERSTORM_CLOUD;
      m_cloudType2=THUNDERSTORM_CLOUD;
      break;
    
    default:
      m_forecast = "clear";
      m_cloudSpacing=0;
      break;
	}
}

void Weather::getCloudSegment(uint8_t cloudIndex, uint8_t cloudSegIndex, 
	unsigned int& strTime, float& strLevel, unsigned int& finTime, float& finLevel,
	unsigned int& bStrTime, float& bStrLevel, unsigned int& bFinTime, float& bFinLevel)
{
	unsigned int clSegStrTime;
	float wClSegStrLevel;
	float bClSegStrLevel;
	unsigned int clSegFinTime;
	float wClSegFinLevel;
	float bClSegFinLevel;
	Segment     clSegStrSeg;
	Segment     clSegFinSeg;
	Segment     bClSegStrSeg;
	Segment     bClSegFinSeg;
	
	switch (m_clouds[cloudIndex].type) 
	{
	  case SHORT_CLOUD:         
	    clSegStrTime = m_shortCloud[cloudSegIndex].time + m_clouds[cloudIndex].start;
	    clSegFinTime = m_shortCloud[cloudSegIndex + 1].time + m_clouds[cloudIndex].start;
	    break;
	
	  case LONG_CLOUD:      
	    clSegStrTime = m_longCloud[cloudSegIndex].time + m_clouds[cloudIndex].start;
	    clSegFinTime = m_longCloud[cloudSegIndex + 1].time + m_clouds[cloudIndex].start;
	    break;    
	
	  case THUNDERSTORM_CLOUD:  
	    clSegStrTime = m_thunderstormCloud[cloudSegIndex].time + m_clouds[cloudIndex].start;
	    clSegFinTime = m_thunderstormCloud[cloudSegIndex + 1].time + m_clouds[cloudIndex].start;
	    break;    
	
	  default: return;    // ERROR!!!  
	}
	
	// Get the segments of the cloud segment start and finish waypoints
	// It is on them that we'll map to find the curve level and then apply the cloud reduction factor
	getSegment(clSegStrTime, clSegStrSeg.strTime, clSegStrSeg.strLevel, clSegStrSeg.finTime, clSegStrSeg.finLevel,
	                         bClSegStrSeg.strTime, bClSegStrSeg.strLevel, bClSegStrSeg.finTime, bClSegStrSeg.finLevel);
	
	getSegment(clSegFinTime, clSegFinSeg.strTime, clSegFinSeg.strLevel, clSegFinSeg.finTime, clSegFinSeg.finLevel,
	                         bClSegFinSeg.strTime, bClSegFinSeg.strLevel, bClSegFinSeg.finTime, bClSegFinSeg.finLevel); 
	                         
	// Map to find original level, then apply reductors
	wClSegStrLevel = mapf(clSegStrTime, clSegStrSeg.strTime, clSegStrSeg.finTime, clSegStrSeg.strLevel, clSegStrSeg.finLevel);
	wClSegFinLevel = mapf(clSegFinTime, clSegFinSeg.strTime, clSegFinSeg.finTime, clSegFinSeg.strLevel, clSegFinSeg.finLevel);
	
	bClSegStrLevel = mapf(clSegStrTime, bClSegStrSeg.strTime, bClSegStrSeg.finTime, bClSegStrSeg.strLevel, bClSegStrSeg.finLevel);
	bClSegFinLevel = mapf(clSegFinTime, bClSegFinSeg.strTime, bClSegFinSeg.finTime, bClSegFinSeg.strLevel, bClSegFinSeg.finLevel);
	
	switch (m_clouds[cloudIndex].type) 
	{
	case SHORT_CLOUD:
	    wClSegStrLevel = (wClSegStrLevel * (100.0 - m_shortCloud[cloudSegIndex].level)/100.0);
	    wClSegFinLevel = (wClSegFinLevel * (100.0 - m_shortCloud[cloudSegIndex+1].level)/100.0);
	    bClSegStrLevel = (bClSegStrLevel * (100.0 - m_shortCloud[cloudSegIndex].level)/100.0);
	    bClSegFinLevel = (bClSegFinLevel * (100.0 - m_shortCloud[cloudSegIndex+1].level)/100.0);
	    break;
	
	case LONG_CLOUD:
	    wClSegStrLevel = (wClSegStrLevel * (100.0 - m_longCloud[cloudSegIndex].level)/100.0);
	    wClSegFinLevel = (wClSegFinLevel * (100.0 - m_longCloud[cloudSegIndex+1].level)/100.0);
	    bClSegStrLevel = (bClSegStrLevel * (100.0 - m_longCloud[cloudSegIndex].level)/100.0);
	    bClSegFinLevel = (bClSegFinLevel * (100.0 - m_longCloud[cloudSegIndex+1].level)/100.0);
	    break;    
	
	case THUNDERSTORM_CLOUD:
	    wClSegStrLevel = (wClSegStrLevel * (100.0 - m_thunderstormCloud[cloudSegIndex].level)/100.0);
	    wClSegFinLevel = (wClSegFinLevel * (100.0 - m_thunderstormCloud[cloudSegIndex+1].level)/100.0);
	    bClSegStrLevel = (bClSegStrLevel * (100.0 - m_thunderstormCloud[cloudSegIndex].level)/100.0);
	    bClSegFinLevel = (bClSegFinLevel * (100.0 - m_thunderstormCloud[cloudSegIndex+1].level)/100.0);
	    break;    
	
	default:
		return;    // ERROR!!!  
	}
	
	strTime  = clSegStrTime;
	strLevel = wClSegStrLevel;
	finTime  = clSegFinTime;
	finLevel = wClSegFinLevel;
	
	bStrTime  = clSegStrTime;
	bStrLevel = bClSegStrLevel;
	bFinTime  = clSegFinTime;
	bFinLevel = bClSegFinLevel;
}

void Weather::getSegment(unsigned int when, 
	unsigned int& wStrTime, float& wStrLevel, unsigned int& wFinTime, float& wFinLevel,
	unsigned int& bStrTime, float& bStrLevel, unsigned int& bFinTime, float& bFinLevel)
{
	int index = 0;
	for (int i=1; i < BASICDAYCURVESIZE ; i++ ) 
	{
		if (when < m_dcwWhiteCurve[i].time) 
		{
			index = i-1;
			break;
		}
	}
	
	wStrTime = m_dcwWhiteCurve[index].time;
	wStrLevel = m_dcwWhiteCurve[index].level;
	wFinTime = m_dcwWhiteCurve[index+1].time;
	wFinLevel = m_dcwWhiteCurve[index+1].level;
	
	index = 0;
	for (int i=1; i < BASICDAYCURVESIZE ; i++ ) 
	{
		if (when < m_dcwBlueCurve[i].time) 
		{
			index = i-1;
			break;
		}
	}
	
	bStrTime = m_dcwBlueCurve[index].time;
	bStrLevel = m_dcwBlueCurve[index].level;
	bFinTime = m_dcwBlueCurve[index+1].time;
	bFinLevel = m_dcwBlueCurve[index+1].level;
}

unsigned int Weather::getCloudDuration(uint8_t type)
{
	switch (type) 
	{
    case SHORT_CLOUD:         
		return m_shortCloud[SHORT_CLOUD_POINTS-1].time;

    case LONG_CLOUD:          
		return m_longCloud[LONG_CLOUD_POINTS-1].time;

    case THUNDERSTORM_CLOUD:  
		return m_thunderstormCloud[THUNDERSTORM_SHAPE_POINTS-1].time;

    default: 
		return 0;
	}
}

uint8_t Weather::insideCloud(unsigned int now)
{
	// if it is still before the first cloud, exit
	if (now <= m_clouds[0].start) 
	{
		return NO_CLOUD;
	}
	
	// First see which clouds starts before now
	byte cloudIndex = NO_CLOUD;
	
	// Loop through the clouds only if now isn't greater than the start of the last cloud
	if (now < m_clouds[m_qtyClouds-1].start) 
	{  
		for (int c=1; c < m_qtyClouds; c++)
		{
			if (now < m_clouds[c].start) 
			{
				cloudIndex = c-1;
				c=m_qtyClouds;      // break the loop
			}
		}
	} 
	else 
	{
		// index set to the last cloud, for now is after it's start
		cloudIndex = (m_qtyClouds-1);
	}
	
	// Then, if there is one starting right before now, check to see if 
	// ir ends after now
	if (cloudIndex != NO_CLOUD) 
	{
		if ((m_clouds[cloudIndex].start + getCloudDuration(m_clouds[cloudIndex].type)) <= now ) 
		{
			// Not inside that cloud....
			cloudIndex = NO_CLOUD;
		}
	}
	
	return cloudIndex;
}

void Weather::planNextCloudBatch(unsigned int now)
{
	if (now <= m_currCloudCoverFinish)
		return;
  
	if (m_okta == 0) 
	{
		// No clouds today
		m_currCloudCoverStart=0;
		m_currCloudCoverFinish=1440U*30U;
		m_qtyClouds=0;    
		return;
	}

	// Space the next cloud batch from the last onw
	m_currCloudCoverStart = m_currCloudCoverFinish + m_cloudSpacing;
  
  
	if ( (now > (1440U*30U/2U)) && ((m_okta == 5) || (m_okta == 6))) 
	{
		// These are special days as afternoon is different from morning
		m_qtyClouds = 1;
		// Start the thunderstorm from one to two hours after midday
		m_clouds[0].start = (1440U*30U/2U) + (uint8_t) random(0U, 120U*30U);
		m_clouds[0].type = THUNDERSTORM_CLOUD;
    
		// Set cloud finish to end of day, to ensure we only get one thunderstorm
		m_currCloudCoverFinish = 1440U*30U;
	} 
	else 
	{
		unsigned int timePos = m_currCloudCoverStart;
		uint8_t cloudCount = 0;

		for (int i=0; i<(MAXCLOUDS/2); i++) 
		{
			if ((timePos > (1440U*30U/2U)) && ((m_okta == 5) || (m_okta == 6))) 
			{
				// Stop the loop if this is an afternoon thunderstorm day
				// and we're past midday
				break;
			} 
			else 
			{
				m_clouds[i*2].start = timePos;
				m_clouds[i*2].type = m_cloudType1;
				timePos = timePos + getCloudDuration(m_cloudType1) + m_cloudSpacing;
              
				m_clouds[i*2 + 1].start = timePos;
				m_clouds[i*2 + 1].type = m_cloudType2;
				timePos = timePos + getCloudDuration(m_cloudType2) + m_cloudSpacing;
				cloudCount = cloudCount+2;
			}
		}

		m_qtyClouds = cloudCount;
		m_currCloudCoverFinish = timePos;
	}
}

void Weather::planBasicCurve(uint8_t aMonth, uint8_t aDay)
{
	unsigned int wSunriseStart, bSunriseStart;
	unsigned int wSunsetFinish, bSunsetFinish;
	unsigned int wFadeDuration, bFadeDuration;
	unsigned int wFadeStep, bFadeStep;
	
	//------------- BASIC CURVE ------------- 
	wFadeDuration = (unsigned int) map((unsigned int) aDay, 1U, (unsigned int) m_daysInMonth[aMonth-1], (unsigned int) m_minFadeDuration[aMonth-1], (unsigned int) m_maxFadeDuration[aMonth-1]);
	bFadeDuration = wFadeDuration + DAWN_DUSK_OFFSET/2U;
	wFadeDuration = wFadeDuration - DAWN_DUSK_OFFSET/2U;
	
	wSunriseStart = TIME_OFFSET + (unsigned int) map((unsigned int) aDay, 1U, (unsigned int) m_daysInMonth[aMonth-1], (unsigned int) m_minSunriseStart[aMonth-1], (unsigned int) m_maxSunriseStart[aMonth-1]);
	bSunriseStart = wSunriseStart - DAWN_DUSK_OFFSET/2U;
	wSunriseStart = wSunriseStart + DAWN_DUSK_OFFSET/2U;
	
	wSunsetFinish = TIME_OFFSET + (unsigned int) map((unsigned int) aDay, 1U, (unsigned int) m_daysInMonth[aMonth-1], (unsigned int) m_minSunsetFinish[aMonth-1], (unsigned int) m_maxSunsetFinish[aMonth-1]);
	bSunsetFinish = wSunsetFinish + DAWN_DUSK_OFFSET/2U;
	wSunsetFinish = wSunsetFinish - DAWN_DUSK_OFFSET/2U;
	
	// 30 transforms "1 min" in "2 secs":
	wFadeDuration = wFadeDuration * 30U;
	bFadeDuration = bFadeDuration * 30U;
	wSunriseStart = wSunriseStart * 30U;
	wSunsetFinish = wSunsetFinish * 30U;
	bSunriseStart = bSunriseStart * 30U;
	bSunsetFinish = bSunsetFinish * 30U;
	wFadeStep = wFadeDuration / 5U;
	bFadeStep = bFadeDuration / 5U;
	
	
	
	m_dcwWhiteCurve[0].time = 0;
	m_dcwWhiteCurve[0].level = 0;
	
	m_dcwWhiteCurve[1].time = wSunriseStart;  
	m_dcwWhiteCurve[1].level = (m_maxWhite * 0) / 100;
	
	m_dcwWhiteCurve[2].time = wSunriseStart + wFadeStep;
	m_dcwWhiteCurve[2].level = (m_maxWhite * 30) / 100;
	
	m_dcwWhiteCurve[3].time = wSunriseStart + 2U*wFadeStep;
	m_dcwWhiteCurve[3].level = (m_maxWhite * 70) / 100;
	
	m_dcwWhiteCurve[4].time = wSunriseStart + 3U*wFadeStep;
	m_dcwWhiteCurve[4].level = (m_maxWhite * 70) / 100;
	
	m_dcwWhiteCurve[5].time = wSunriseStart + 4U*wFadeStep;
	m_dcwWhiteCurve[5].level = m_maxWhite;
	
	m_dcwWhiteCurve[6].time = wSunriseStart + 5U*wFadeStep;
	m_dcwWhiteCurve[6].level = m_maxWhite;
	
	m_dcwWhiteCurve[7].time = wSunsetFinish - 5U*wFadeStep;
	m_dcwWhiteCurve[7].level = m_maxWhite;
	
	m_dcwWhiteCurve[8].time = wSunsetFinish - 4U*wFadeStep;
	m_dcwWhiteCurve[8].level = m_maxWhite;
	
	m_dcwWhiteCurve[9].time = wSunsetFinish - 3U*wFadeStep;
	m_dcwWhiteCurve[9].level = m_maxWhite;
	
	m_dcwWhiteCurve[10].time = wSunsetFinish - 2U*wFadeStep;
	m_dcwWhiteCurve[10].level = (m_maxWhite * 70) / 100;
	
	m_dcwWhiteCurve[11].time = wSunsetFinish - wFadeStep;
	m_dcwWhiteCurve[11].level = (m_maxWhite * 30) / 100;
	
	m_dcwWhiteCurve[12].time = wSunsetFinish;
	m_dcwWhiteCurve[12].level = (m_maxWhite * 0) / 100;
	
	m_dcwWhiteCurve[13].time = 1440U * 30U;
	m_dcwWhiteCurve[13].level = 0;
	
	
	m_dcwBlueCurve[0].time = 0;
	m_dcwBlueCurve[0].level = 0;
	
	m_dcwBlueCurve[1].time = bSunriseStart;  
	m_dcwBlueCurve[1].level = (m_maxBlue * 10) / 100;
	
	m_dcwBlueCurve[2].time = bSunriseStart + bFadeStep;
	m_dcwBlueCurve[2].level = (m_maxBlue * 30) / 100;
	
	m_dcwBlueCurve[3].time = bSunriseStart + 2U*bFadeStep;
	m_dcwBlueCurve[3].level = (m_maxBlue * 70) / 100;
	
	m_dcwBlueCurve[4].time = bSunriseStart + 3U*bFadeStep;
	m_dcwBlueCurve[4].level = (m_maxBlue * 90) / 100;
	
	m_dcwBlueCurve[5].time = bSunriseStart + 4U*bFadeStep;
	m_dcwBlueCurve[5].level = m_maxBlue;
	
	m_dcwBlueCurve[6].time = bSunriseStart + 5U*bFadeStep;
	m_dcwBlueCurve[6].level = m_maxBlue;
	
	m_dcwBlueCurve[7].time = bSunsetFinish - 5U*bFadeStep;
	m_dcwBlueCurve[7].level = m_maxBlue;
	
	m_dcwBlueCurve[8].time = bSunsetFinish - 4U*bFadeStep;
	m_dcwBlueCurve[8].level = m_maxBlue;
	
	m_dcwBlueCurve[9].time = bSunsetFinish - 3U*bFadeStep;
	m_dcwBlueCurve[9].level = (m_maxBlue * 90) / 100;
	
	m_dcwBlueCurve[10].time = bSunsetFinish - 2U*bFadeStep;
	m_dcwBlueCurve[10].level = (m_maxBlue * 70) / 100;
	
	m_dcwBlueCurve[11].time = bSunsetFinish - bFadeStep;
	m_dcwBlueCurve[11].level = (m_maxBlue * 30) / 100;
	
	m_dcwBlueCurve[12].time = bSunsetFinish;
	m_dcwBlueCurve[12].level = (m_maxBlue * 0.1) / 100;
	
	m_dcwBlueCurve[13].time = 1440U * 30U;
	m_dcwBlueCurve[13].level = (m_maxBlue * 0) / 100;
}

void Weather::planNewDay(uint8_t aMonth, uint8_t aDay)
{
	planBasicCurve(aMonth, aDay);

    //------------- OKTA DETERMINATION  ------------- 
    uint8_t randNumber = (uint8_t) random(0,100);
  
    if (randNumber > m_cloudyDays[aMonth]) 
		// this is a mixed day, Okta 2 to 3
		m_okta = (byte) random(2,4);

    else if (randNumber > m_clearDays[aMonth] )
		// this is a cloudy day, Okta 4 to 8
		m_okta = (byte) random(4,9);

    else
		// this is a clear day, Okta 0 to 1
		m_okta = (byte) random(0,2);

	setCloudSpacingAndTypes();

	m_currCloudCoverFinish = 0;
}

void Weather::getLevel(unsigned int now, bool& inThunderstorm, float& whiteLevel, float& blueLevel)
{
	uint8_t cloudIndex;
	uint8_t cloudSegIndex;
	Segment wSeg;
	Segment bSeg;
	
	inThunderstorm = false;
	cloudIndex = insideCloud(now);
	
	if (cloudIndex == NO_CLOUD) 
	{
	    // Not in a cloud, just map the position to the basic day curve
	    getSegment(now, wSeg.strTime, wSeg.strLevel, wSeg.finTime, wSeg.finLevel,
	                    bSeg.strTime, bSeg.strLevel, bSeg.finTime, bSeg.finLevel);
	} 
	else 
	{
	    // OK, we're in a cloud....
	    // Get first cloud segment
	    cloudSegIndex = 0;
	    getCloudSegment(cloudIndex, cloudSegIndex, wSeg.strTime, wSeg.strLevel, wSeg.finTime, wSeg.finLevel,
	                                               bSeg.strTime, bSeg.strLevel, bSeg.finTime, bSeg.finLevel);
	  
	    while (wSeg.finTime < now) 
		{
	        // now isn't in this cloud segment, so get the next one and check
	        cloudSegIndex++;
	        getCloudSegment(cloudIndex, cloudSegIndex, wSeg.strTime, wSeg.strLevel, wSeg.finTime, wSeg.finLevel,
	                                                   bSeg.strTime, bSeg.strLevel, bSeg.finTime, bSeg.finLevel);
	    }
	  
	    // Inform if we're in a thunderstorm cloud
	    if (m_clouds[cloudIndex].type == THUNDERSTORM_CLOUD) 
	        inThunderstorm = true;
	}
	
	whiteLevel = mapf(now, wSeg.strTime, wSeg.finTime, wSeg.strLevel, wSeg.finLevel);
	blueLevel = mapf(now, bSeg.strTime, bSeg.finTime, bSeg.strLevel, bSeg.finLevel);
}

void printWithLeadingZero(byte number) 
{
	if (number < 10) 
	{
		Serial.print("0");
		Serial.print(number,DEC);
	} 
	else 
	{
		Serial.print(number,DEC);
	}
}

void printCurveTime(unsigned int aTime) 
{
	unsigned int anHour = aTime / 1800U;
	unsigned int aMinute = (aTime - anHour*1800U) / 30U;
	unsigned int aSecond = aTime - anHour*1800U - aMinute*30U;

	printWithLeadingZero(anHour);
	Serial.print(":");
	printWithLeadingZero(aMinute);
	Serial.print(":");
	printWithLeadingZero(aSecond);
}

void Weather::changeOkta(uint8_t okta)
{
	m_okta = okta;
	setCloudSpacingAndTypes();
	m_currCloudCoverFinish = 0;
}

/******************************************************************************************
 * DUMP CURVE
 *
 * Print out to the serial port today's dcwWhiteCurve
 **/
void Weather::dumpCurve() 
{
	Serial.println("DUMP CURVE:");
	Serial.println("Index\tTime\tLevel");
	for (int i=0; i < BASICDAYCURVESIZE; i++) 
	{
		Serial.print(i, DEC);
		Serial.print("\t");
		printCurveTime(m_dcwWhiteCurve[i].time);
		Serial.print("\t");
		Serial.print(m_dcwWhiteCurve[i].time, DEC);
		Serial.print("\t");
		Serial.print(m_dcwWhiteCurve[i].level, DEC);
		Serial.println();
	}
	Serial.println("END W");

	for (int i=0; i < BASICDAYCURVESIZE; i++) 
	{
		Serial.print(i, DEC);
		Serial.print("\t");
		printCurveTime(m_dcwBlueCurve[i].time);
		Serial.print("\t");
		Serial.print(m_dcwBlueCurve[i].time, DEC);
		Serial.print("\t");
		Serial.print(m_dcwBlueCurve[i].level, DEC);
		Serial.println();
	}
	Serial.println("END B");
	Serial.println();
}